#define _LARGEFILE64_SOURCE
#include <libavformat/avio.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>

#define COMMAND_FCLOSE (1)
#define COMMAND_FSEEK (2)
#define COMMAND_FREAD (3)

struct priv_dat {
    int sock;
    int64_t filesize;
};

int e2url_open(URLContext *h, const char *filename, int flags);
int e2url_read(URLContext *h, unsigned char *buf, int size);
int e2url_write(URLContext *h, unsigned char *buf, int size);
int64_t e2url_seek(URLContext *h, int64_t pos, int whence);
int e2url_close(URLContext *h);

int is_eof = 0;

int handle_error(char *msg) {
    printf("\nerror: %s\n",msg);
    return -1;
}

/**
 * This function is a combination of select() and recv().
 * It gets the same arguments as recv() with addition to one more - int secs,
 * which indicates the timeout we should set to the select() function. It calls
 * handle_error if timeout was reached and therefore should only be called when
 * we're sure a packet is coming (by protocol), and returns the recv()
 * return code if a timeout was not reached in select().
 * */
int my_recv(int sock, void *buf, int buflen, int flags, int secs) {
    struct timeval timeout;
    fd_set readfds;
    int status;
    int bytesRCVD=0, rcvdNOW=0;

    while (bytesRCVD < buflen) {
        /* select() until it returns with something other than a EINTR error code
         * (which is one we should ignore) */
        do {
            timeout.tv_sec = secs;
            timeout.tv_usec = 0;
            FD_ZERO(&readfds);
            FD_SET(sock, &readfds);
            status = select(sock+1, &readfds, NULL, NULL, &timeout);
        } while ((status < 0)&&(errno == EINTR));
        if (status<0)
            handle_error("my_recv()->select()\n");

        if ((status == 0) || (!FD_ISSET(sock, &readfds)))
            handle_error("select() timed-out in my_recv(), something is wrong.\n");
        if (0 > (rcvdNOW = recv(sock, &buf[bytesRCVD], buflen-bytesRCVD, flags)))
            handle_error("my_recv()->recv()");
        bytesRCVD += rcvdNOW;
    }
    return buflen;
}

URLProtocol e2URLProtocol = {
	.name = "osetwo",			//name must be alphabetic.
	.url_open = e2url_open,
	.url_read = e2url_read,
	.url_write = e2url_write,
	.url_seek = e2url_seek,
	.url_close = e2url_close,

};

int e2url_open(URLContext *h, const char *filename, int flags)
{
    struct priv_dat *privdat;
    struct sockaddr_in serverAddr;

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(1337);
    serverAddr.sin_addr.s_addr = inet_addr(&filename[9]);

    if (NULL == (privdat = (struct priv_dat*)malloc(sizeof(struct priv_dat))))
        return handle_error("e2url_open()->malloc()");
    privdat->filesize = -1;
    if (0 > (privdat->sock = socket(AF_INET, SOCK_STREAM, 0)))
        return handle_error("e2url_open()->socket()");
    if (-1 == connect(privdat->sock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)))
        return handle_error("e2url_open()->connect()");

    h->priv_data = (void*)privdat;
    return 0;
}

int e2url_read(URLContext *h, unsigned char *buf, int size)
{
    int numbytes, recvstatus;
    char cmd = COMMAND_FREAD;
    int mysock = ((struct priv_dat*)h->priv_data)->sock;
    printf("e2url_read() called, size=%d", size);

    // Send the FREAD command and the size argument
    if ((-1 == send(mysock, (void*)&cmd, 1, 0)) ||
        (-1 == send(mysock, (void*)&size, 4, 0)))
        return handle_error("e2url_read()->send()");

    // Get number of bytes to read and the buffer itself
    if ((-1 == my_recv(mysock, (void*)&numbytes, 4, 0, 20)) ||
        ((numbytes>0) && (-1 == (recvstatus = my_recv(mysock, buf, numbytes, 0, 20)))))
        return handle_error("e2url_read()->recv()");
    printf(", recv=%d, ret=%d\n",recvstatus, numbytes);

    return numbytes;
}

int e2url_write(URLContext *h, unsigned char *buf, int size)
{
	//Not implemented.
	return -1;
}

int64_t e2url_seek(URLContext *h, int64_t pos, int whence)
{
    int64_t retval;
    int rstat;
    char cmd = COMMAND_FSEEK;
    int mysock = ((struct priv_dat*)h->priv_data)->sock;


    if ((((struct priv_dat*)h->priv_data)->filesize != -1) &&
            (AVSEEK_SIZE != whence) &&
            (pos >= ((struct priv_dat*)h->priv_data)->filesize))
        return pos-1;

    // Send FSEEK command, pos and whence arguments.
    printf("e2url_seek() called: pos=%lld, whence=%d",pos,whence);
    if ((-1 == send(mysock, (void*)&cmd, 1, 0)) ||
        (-1 == send(mysock, (void*)&pos, 8, 0)) ||
        (-1 == send(mysock, (void*)&whence, 4, 0)))
        return (int64_t)handle_error("e2url_seek()->send()");

    // Receive return value for fseeko64() from server.
    if (-1 == (rstat = my_recv(mysock, (void*)&retval, 8, 0, 20)))
        return (int64_t)handle_error("e2url_seek()->recv()");

    printf(", recv=%d, ret=%d\n",rstat, retval);
    if (whence == AVSEEK_SIZE)
        ((struct priv_dat*)h->priv_data)->filesize = retval;

    return retval;
}

int e2url_close(URLContext *h)
{
    int mysock = ((struct priv_dat*)h->priv_data)->sock;
    char cmd = COMMAND_FCLOSE;

    /* We are kind enough to tell the server that we're closing, but it's not
     that big of a deal if it doesn't get it. */
    send(mysock, &cmd, 1, 0);
    close(mysock);
    free(h->priv_data);
}

